package org.atlantis.grosbeak.pot;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.atlantis.grosbeak.lang.Exceptions;
import org.atlantis.grosbeak.lang.Mirror;
import org.atlantis.grosbeak.pot.dialect.Dialect;
import org.atlantis.grosbeak.pot.elm.FirstIndex;
import org.atlantis.grosbeak.pot.elm.MappedBy;
import org.atlantis.grosbeak.pot.elm.MaxIndex;
import org.atlantis.grosbeak.pot.elm.Query;
import org.atlantis.grosbeak.pot.elm.Unique;
import org.atlantis.grosbeak.pot.mapper.ArrayRowMapper;
import org.atlantis.grosbeak.pot.mapper.RowMapper;
import org.atlantis.grosbeak.pot.transaction.TransactionProvider;

/**
 * 用于执行查询操作的SQL执行器。
 * 
 * @author <a href="mailto:seenoevil.cn@gmail.com">SeeNoEvil</a>
 * 
 */
class QuerySQLHandler extends SQLHandler {

	// 预处理的查询SQL语句
	private String preparedQuery;
	private int fetchSize;
	// unique表示返回的结果是不是唯一的，如果不是的就返回一个List
	private boolean unique;
	private int[] argIndex;
	private Method[] argGets;
	private RowMapper<?> rm;

	/**
	 * 实例化一个 QuerySQLHandler。
	 * 
	 * @param method
	 * @param dialect
	 * @throws PotException
	 */
	public QuerySQLHandler(Method method, Dialect dialect) throws PotException {
		Query query = method.getAnnotation(Query.class);
		String sqlQuery = query.value();
		fetchSize = query.fetchSize();
		if (sqlQuery.trim().length() == 0) {
			throw Exceptions.makeThrow(PotException.class,
					"Missing query language in @Query on method: '%s'", method
							.getName());
		}
		Annotation[] anns = getArgsAnnotations(method);
		int indexOfFirstIndex = -1;
		int indexOfMaxIndex = -1;
		for (int i = 0; i < anns.length; i++) {
			Annotation ann = anns[i];
			if (ann.annotationType().equals(FirstIndex.class)) {
				if (indexOfFirstIndex == -1)
					indexOfFirstIndex = i;
			} else if (ann.annotationType().equals(MaxIndex.class)) {
				if (indexOfMaxIndex == -1)
					indexOfMaxIndex = i;
			}
		}

		// 检查参数是否都正确
		Class<?>[] paramTypes = method.getParameterTypes();
		if (indexOfFirstIndex != -1 && !isInt(paramTypes[indexOfFirstIndex]))
			throw Exceptions
					.makeThrow(
							PotException.class,
							"@FirstIndex can not apply any type but int, in method: '%s'.",
							method.getName());
		if (indexOfMaxIndex != -1 && !isInt(paramTypes[indexOfMaxIndex]))
			throw Exceptions
					.makeThrow(
							PotException.class,
							"@MaxIndex can not apply any type but int, in method: '%s'.",
							method.getName());

		// 开始构建查询语句
		String[] argNames = PotKit.getParamNames(sqlQuery);
		this.preparedQuery = buildQuery(sqlQuery, (indexOfFirstIndex != -1),
				(indexOfMaxIndex != -1), dialect);
		this.unique = (method.getAnnotation(Unique.class) != null);

		// 设置@FirstIndex和@MaxIndex的值
		int[] limitIndex = null;
		if (indexOfFirstIndex != -1 && indexOfMaxIndex != -1) {
			boolean switchLimitParams = dialect
					.bindLimitParametersInReverseOrder();
			limitIndex = new int[2];
			limitIndex[0] = switchLimitParams ? indexOfMaxIndex
					: indexOfFirstIndex;
			limitIndex[1] = switchLimitParams ? indexOfFirstIndex
					: indexOfMaxIndex;
		} else if (indexOfFirstIndex != -1) {
			limitIndex = new int[1];
			limitIndex[0] = indexOfFirstIndex;
		} else if (indexOfMaxIndex != -1) {
			limitIndex = new int[1];
			limitIndex[0] = indexOfMaxIndex;
		}

		// 初始化参数值
		int[] paramIndex = new int[argNames.length];
		Method[] paramMethods = new Method[argNames.length];
		for (int i = 0; i < argNames.length; i++) {
			String argName = argNames[i];
			int pos = getIndexOfArgByName(method, anns, argName);
			paramIndex[i] = pos;
			String prop = getPropertyName(argName);
			if (prop != null)
				paramMethods[i] = getGetterMethod(paramTypes[pos], prop);
		}

		this.argIndex = new int[paramIndex.length
				+ (limitIndex == null ? 0 : limitIndex.length)];
		this.argGets = new Method[argIndex.length];
		if (limitIndex == null) {
			System.arraycopy(paramIndex, 0, argIndex, 0, paramIndex.length);
			System.arraycopy(paramMethods, 0, argGets, 0, paramMethods.length);
		} else {
			if (dialect.bindLimitParametersFirst()) {
				System.arraycopy(limitIndex, 0, this.argIndex, 0,
						limitIndex.length);
				System.arraycopy(paramIndex, 0, this.argIndex,
						limitIndex.length, paramIndex.length);
				System.arraycopy(paramMethods, 0, this.argGets,
						limitIndex.length, paramMethods.length);
			} else {
				System.arraycopy(paramIndex, 0, this.argIndex, 0,
						paramIndex.length);
				System.arraycopy(paramMethods, 0, this.argGets, 0,
						paramMethods.length);
				System.arraycopy(limitIndex, 0, this.argIndex,
						paramIndex.length, limitIndex.length);
			}
		}

		MappedBy mappedBy = method.getAnnotation(MappedBy.class);
		try {
			this.rm = (mappedBy == null) ? new ArrayRowMapper() : mappedBy
					.value().newInstance();
		} catch (Exception e) {
			throw Exceptions.makeThrow(PotException.class,
					"Unable to create RowMapper instance for method: '%s'",
					method.getName());
		}
		checkReturnType(method);
	}

	/**
	 * 构建一个标准的JDBC中使用的SQL语句。
	 * 
	 * @param sqlQuery
	 *            原始的的SQL查询语句。
	 * @param hasFirstIndex
	 *            是否有firstIndex参数。
	 * @param hasMaxIndex
	 *            是否有maxIndex参数。
	 * @param dialect
	 *            数据库方言。
	 * @return
	 */
	private String buildQuery(String sqlQuery, boolean hasFirstIndex,
			boolean hasMaxIndex, Dialect dialect) {
		String preparedQuery = PotKit.getPreparedSql(sqlQuery).trim();
		if (!hasFirstIndex && !hasMaxIndex)
			return preparedQuery;
		return dialect.localizeLimitSQL(preparedQuery, hasFirstIndex,
				hasMaxIndex);
	}

	/**
	 * 检查返回值是否正确。
	 * 
	 * @param method
	 * @throws PotException
	 */
	private void checkReturnType(Method method) throws PotException {
		Class<?> returnType = method.getReturnType();
		if (this.unique) {
			Class<?> expectedType = PotKit.getRowMapperGenericType(this.rm
					.getClass());
			if (returnType.isPrimitive())
				returnType = Mirror.me(returnType).getWrapperClass();
			if (!returnType.isAssignableFrom(expectedType))
				throw Exceptions
						.makeThrow(
								PotException.class,
								"Return type is expected as '%s' width @Unique, but actually is '%s'.",
								expectedType.getName(), returnType.getName());
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.atlantis.grosbeak.pot.SQLHandler#handle(org.atlantis.grosbeak.pot
	 * .transaction.TransactionProvider, java.lang.Object[])
	 */
	@Override
	public Object handle(TransactionProvider txProvider, Object... args)
			throws PotException {
		return new BaseQueryCallback<Object>() {

			@Override
			protected Object ctrlResult(ResultSet rs) throws SQLException {
				ResultSetMetaData rsmd = rs.getMetaData();
				int colCount = rsmd.getColumnCount();
				String[] colNames = new String[colCount];
				int[] colTypes = new int[colCount];
				for (int i = 0; i < colCount; i++) {
					colNames[i] = rsmd.getColumnName(i + 1);
					colTypes[i] = rsmd.getColumnType(i + 1);
				}
				if (unique) {
					if (rs.next()) {
						Object result = rm.mapRow(rs, colNames, colTypes);
						if (rs.next()) {
							throw Exceptions.makeThrow(PotException.class,
									"Query returns more than one results.");
						}
						return result;
					}
					return null;
				}
				List<Object> list = new ArrayList<Object>(getFetchSize());
				while (rs.next()) {
					list.add(rm.mapRow(rs, colNames, colTypes));
				}
				return list;
			}

			@Override
			protected int getFetchSize() {
				return unique ? 2 : fetchSize;
			}
		}.execute(txProvider, preparedQuery, getSqlParams(argIndex, argGets,
				args));
	}
}
